import random
import operator
import copy

from eap import base
from eap import creator
from eap import toolbox
import matplotlib.pyplot as pyplot

creator.create("FitnessMax", base.Fitness, weights=(1.0,))
creator.create("Individual", list, fitness=creator.FitnessMax)
creator.create("Population", list)

tools = toolbox.Toolbox()
# Attribute generator
tools.register("attr_bool", random.randint, 0, 1)
# Structure initializers
tools.register("individual", creator.Individual,
    content_init=tools.attr_bool, size_init=100)
tools.register("population", creator.Population,
    content_init=tools.individual, size_init=100)


def doubleMaximaUneven(individual):
    return abs(sum(individual) - 40),

def doubleEvenMaximaUneven(individual):
    x = sum(individual)
    if (x < 33):
        return abs(x - 33)*2,
    return abs(x-33),
    
def doubleMaximaRightHeavy(individual):
    """f(50) is zero, f(0) is 25, f(100) is 100"""
    x = sum(individual)
    landscape = abs(x - 50)
    if x < 50:
        return landscape,
    else:
        return landscape*1.5,

# Operator registering
tools.register("evaluate", doubleMaximaUneven)
tools.register("mate", toolbox.cxTwoPoints)
tools.register("mutate", toolbox.mutFlipBit, indpb=0.05)
tools.register("select", toolbox.selTournament, tournsize=3)




def fitnessgenericGeneration(pop):
        tools.register("select", toolbox.selRoulette)
        #tools.register("select", toolbox.selBest)
        CXPB, MUTPB, NGEN = 0.5, 0.2, 100
        threshold = 100
        offsprings = []
        for i in range(len(pop)/2):
            children = chooseCloseParents(pop, threshold)
            offsprings += children
            
        
        offsprings = map(tools.clone, offsprings)
    # Apply crossover and mutation on the offsprings
        for child1, child2 in zip(offsprings[::2], offsprings[1::2]):
            if random.random() < CXPB:
                tools.mate(child1, child2)
                del child1.fitness.values
                del child2.fitness.values

        valid_ind = [ind for ind in offsprings if ind.fitness.valid]
        for mutant in valid_ind:
            if random.random() < MUTPB:
                tools.mutate(mutant)
                del mutant.fitness.values
    
        # Evaluate the individuals with an invalid fitness
        invalid_ind = [ind for ind in offsprings if not ind.fitness.valid]
        fitnesses = map(tools.evaluate, invalid_ind)
        for ind, fit in zip(invalid_ind, fitnesses):
            ind.fitness.values = fit
        
        #print "  Evaluated %i individuals" % len(invalid_ind)

        
        # The population is entirely replaced by the offsprings
        pop[:] = offsprings
        fitnessesShared(pop)
        pop = tools.select(pop, n=len(pop))
        
        
        # Gather all the fitnesses in one list and print the stats

def fitnessesShared(population):
    
    fitnesses = map(tools.evaluate, population)
    for ind, fit in zip(population, fitnesses):
        ind.fitness.values = fit
    
    nicheCounts = [nicheCount(ind, population) for ind in population]
    for ind, NC in zip(population, nicheCounts):
        ind.fitness.values = ind.fitness.values[0]/NC,
    
def nicheCount(individual, population):
    count = 0
    threshold = 5
    someAlpha = 1
    for ind in population:
        r = threshold
        d = distance(individual, ind)
        if d > r:
            numerator = 0
        else:
            numerator = (r - d)**someAlpha
        denominator = threshold
        
        count += numerator/denominator
    return count
    
def distance(ind1, ind2):
    """
    Calculate the distance between two individuals.
    Doesn't use Hamming Distance, but instead bases it off
    of the distance between the individuals in the search space.
    """
    d1 = sum(ind1)
    d2 = sum(ind2)
    return abs(d2-d1)


def chooseCloseParents(population, threshold):
    """
    With a given population, selects two random parents
    from the given population and returns them to the caller.
    """
    random.shuffle(population)
    firstParent = population.pop(0)
    for possibleParent in population:
        #if distance(firstParent, possibleParent) < threshold:
        if abs(firstParent.fitness.values[0] - possibleParent.fitness.values[0]) < threshold:
            population.remove(possibleParent)
            return [firstParent,possibleParent]
    population.remove(possibleParent)
    return [firstParent,possibleParent]


def runTest(generations,fitnessLandscape,selectionType,generationType):
    """Runs a test with the given parameters"""
    random.seed(3)
    
    tools.register("evaluate", fitnessLandscape)
    tools.register("doGeneration", generationType)
    
    """Set the title name for the tests, labeling the graphs"""
    testTitle = str(tools.evaluate.__name__)+"\n"+str(tools.doGeneration.__name__)
    
    """Initialize the population"""
    pop = tools.population()

    """ These forloops give an even distribution in the search space """
    
    
    for i in range(len(pop)):
        for j in range(i):
            pop[i][j] = 0
        for j in range(i,100):
            pop[i][j] = 1
        random.shuffle(pop[i])
    
    '''
    for i in pop:
        print i
    '''
    
    """ generates an even distribution population for the purpose of drawing a line on
    the graphs"""
    landscape = pop[:]
    for i in range(len(landscape)):
        for j in range(i):
            landscape[i][j] = 1
        for j in range(i,100):
            landscape[i][j] = 0
            
    CXPB, MUTPB, NGEN = 0.5, 0.2, generations
    
    print "Start of evolution"
    print testTitle
    
    # Evaluate the entire population
    fitnesses = map(tools.evaluate, pop)
    for ind, fit in zip(pop, fitnesses):
        ind.fitness.values = fit
    
    print "  Evaluated %i individuals" % len(pop)
    globalEvaluations = 0

    pyplot.ion()
    landscape = map(tools.evaluate, landscape)
    landscape = [item[0] for item in landscape]
    #print landscape, "items:",len(landscape)
    display = pyplot.figure()



    interval = generations/5
    display.add_subplot(NGEN/interval+1,1,0/interval+1)
    pyplot.scatter(range(100),landscape, s=1)

    # Begin the evolution
    for g in range(NGEN+1):
        #print "-- Generation %i --" % g
        tools.doGeneration(pop)
        
        
        fits = [ind.fitness.values[0] for ind in pop]

        """
        genotypeFits is the list of individuals based on their location
        in the search space
        """
        genotypeFits = [sum(ind) for ind in pop]
        
        if g % interval == 0:
            """At regular intervals, display progress so far."""
            counts = [0 for i in range(101)] #Initialize the population histogram
            for f in genotypeFits:
                counts[int(f)] += 1  #Keep track of fitness frequencies
            display.add_subplot(NGEN/interval+1,1,g/interval+1)
            pyplot.bar(range(101),counts)
            pyplot.suptitle(testTitle)
            pyplot.xlim(0,101)
            pyplot.ylim(0,100)
            pyplot.draw()
        
        #print "  Min %s" % min(fits)
        #print "  Max %s" % max(fits)
        #print "  Avg %s" % mean
        #print "  Std %s" % std_dev

    
    print "-- End of (successful) evolution --"
    
    best_ind = toolbox.selBest(pop, 1)[0]
    #print "Best individual is %s, %s" % (best_ind, best_ind.fitness.values)

    return (best_ind, best_ind.fitness.values) , globalEvaluations


if __name__ == "__main__":
    resultList = []
    #resultList.append(base(2))
    
    
    runTest(200,doubleEvenMaximaUneven,None,fitnessgenericGeneration)
    runTest(200,doubleMaximaUneven,None,fitnessgenericGeneration)
    runTest(200,doubleMaximaRightHeavy,None,fitnessgenericGeneration)
    #runTest(100,evalOneMax,None,crowdingGeneration)
    #runTest(5000,sherriFitnessLandscape,None,crowdingGeneration)
    

    #runTest(200,sherriFitnessLandscape,None,crowdingGeneration)
    #runTest(200,middlePeak,None,crowdingGeneration)
    #runTest(6000,doubleEvenMaximaUneven,None,crowdingGeneration)
    #runTest(6000,doubleMaximaUneven,None,crowdingGeneration)
    #runTest(6000,doubleMaximaRightHeavy,None,crowdingGeneration)
    
    raw_input("continue")